/*
 *
 *   Copyright (C) 2005 by Raymond Huang
 *   plushuang at users.sourceforge.net
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *  ---
 *
 *  In addition, as a special exception, the copyright holders give
 *  permission to link the code of portions of this program with the
 *  OpenSSL library under certain conditions as described in each
 *  individual source file, and distribute linked combinations
 *  including the two.
 *  You must obey the GNU Lesser General Public License in all respects
 *  for all of the code used other than OpenSSL.  If you modify
 *  file(s) with this exception, you may extend this exception to your
 *  version of the file(s), but you are not obligated to do so.  If you
 *  do not wish to do so, delete this exception statement from your
 *  version.  If you delete this exception statement from all source
 *  files in the program, then also delete it here.
 *
 */

/*
 *  Code base on Gtk+ 2.0 Tree View tutorial
 *      - a tutorial covering GtkTreeView by Tim-Philipp Mu"ller
 */ 


#include "item_list_model.h"

/* boring declarations of local functions */

static void         item_list_model_init            (ItemListModel      *pkg_tree);

static void         item_list_model_class_init      (ItemListModelClass *klass);

static void         item_list_model_tree_model_init (GtkTreeModelIface *iface);

static void         item_list_model_finalize        (GObject           *object);

static GtkTreeModelFlags item_list_model_get_flags  (GtkTreeModel      *tree_model);

static gint         item_list_model_get_n_columns   (GtkTreeModel      *tree_model);

static GType        item_list_model_get_column_type (GtkTreeModel      *tree_model,
                                                     gint               index);

static gboolean     item_list_model_get_iter        (GtkTreeModel      *tree_model,
                                                     GtkTreeIter       *iter,
                                                     GtkTreePath       *path);

static GtkTreePath *item_list_model_get_path        (GtkTreeModel      *tree_model,
                                                     GtkTreeIter       *iter);

static void         item_list_model_get_value       (GtkTreeModel      *tree_model,
                                                     GtkTreeIter       *iter,
                                                     gint               column,
                                                     GValue            *value);

static gboolean     item_list_model_iter_next       (GtkTreeModel      *tree_model,
                                                     GtkTreeIter       *iter);

static gboolean     item_list_model_iter_children   (GtkTreeModel      *tree_model,
                                                     GtkTreeIter       *iter,
                                                     GtkTreeIter       *parent);

static gboolean     item_list_model_iter_has_child  (GtkTreeModel      *tree_model,
                                                     GtkTreeIter       *iter);

static gint         item_list_model_iter_n_children (GtkTreeModel      *tree_model,
                                                     GtkTreeIter       *iter);

static gboolean     item_list_model_iter_nth_child  (GtkTreeModel      *tree_model,
                                                     GtkTreeIter       *iter,
                                                     GtkTreeIter       *parent,
                                                     gint               n);

static gboolean     item_list_model_iter_parent     (GtkTreeModel      *tree_model,
                                                     GtkTreeIter       *iter,
                                                     GtkTreeIter       *child);

static GObjectClass *parent_class = NULL;  /* GObject stuff - nothing to worry about */


/*****************************************************************************
 *
 *                        here we register our new type and its interfaces
 *                        with the type system. If you want to implement
 *                        additional interfaces like GtkTreeSortable, you
 *                        will need to do it here.
 *
 *****************************************************************************/

GType item_list_model_get_type (void)
{
	static GType item_list_model_type = 0;

	if (item_list_model_type)
		return item_list_model_type;

	/* Some boilerplate type registration stuff */
	if (1) {
		static const GTypeInfo item_list_model_info = {
			sizeof (ItemListModelClass),
			NULL,                                         /* base_init */
			NULL,                                         /* base_finalize */
			(GClassInitFunc) item_list_model_class_init,
			NULL,                                         /* class finalize */
			NULL,                                         /* class_data */
			sizeof (ItemListModel),
			0,                                            /* n_preallocs */
			(GInstanceInitFunc) item_list_model_init
		};

		item_list_model_type = g_type_register_static (G_TYPE_OBJECT,
		                                               "ItemListModel",
		                                               &item_list_model_info,
		                                               (GTypeFlags)0);
	}

	if (1) {
		static const GInterfaceInfo tree_model_info = {
			(GInterfaceInitFunc) item_list_model_tree_model_init,
			NULL,
			NULL
		};

		g_type_add_interface_static (item_list_model_type,
		                             GTK_TYPE_TREE_MODEL,
		                             &tree_model_info);
	}

	return item_list_model_type;
}


/*****************************************************************************
 *
 *                          more boilerplate GObject/GType stuff.
 *                          Init callback for the type system,
 *                          called once when our new class is created.
 *
 *****************************************************************************/

static void item_list_model_class_init (ItemListModelClass *klass)
{
	GObjectClass* object_class;

	// parent_class is a global value...
	parent_class = (GObjectClass*) g_type_class_peek_parent (klass);
	object_class = (GObjectClass*) klass;

	object_class->finalize = item_list_model_finalize;
}

/*****************************************************************************
 *
 *                               init callback for the interface registration
 *                               in custom_list_get_type. Here we override
 *                               the GtkTreeModel interface functions that
 *                               we implement.
 *
 *****************************************************************************/

static void
item_list_model_tree_model_init (GtkTreeModelIface *iface)
{
	iface->get_flags       = item_list_model_get_flags;
	iface->get_n_columns   = item_list_model_get_n_columns;
	iface->get_column_type = item_list_model_get_column_type;
	iface->get_iter        = item_list_model_get_iter;
	iface->get_path        = item_list_model_get_path;
	iface->get_value       = item_list_model_get_value;
	iface->iter_next       = item_list_model_iter_next;
	iface->iter_children   = item_list_model_iter_children;
	iface->iter_has_child  = item_list_model_iter_has_child;
	iface->iter_n_children = item_list_model_iter_n_children;
	iface->iter_nth_child  = item_list_model_iter_nth_child;
	iface->iter_parent     = item_list_model_iter_parent;
}


/*****************************************************************************
 *
 *  item_list_model_init: this is called everytime a new custom list object
 *                    instance is created (we do that in item_list_model_new).
 *                    Initialise the list structure's fields here.
 *
 *****************************************************************************/

static void item_list_model_init (ItemListModel *item_list_model)
{
	/* Random int to check whether an iter belongs to our model */
	item_list_model->stamp = g_random_int();
}


/*****************************************************************************
 *
 *  item_list_model_finalize: this is called just before a custom list is
 *                        destroyed. Free dynamically allocated memory here.
 *
 *****************************************************************************/

static void item_list_model_finalize (GObject *object)
{
	ItemListModel* model = ITEM_LIST_MODEL (object);

	url_item_list_added_disconnect (model->item_list, model->added_signal);
	url_item_list_unref (model->item_list);
	(* parent_class->finalize) (object);
}


/*****************************************************************************
 *
 *  item_list_model_get_flags: tells the rest of the world whether our tree model
 *                         has any special characteristics. In our case,
 *                         we have a list model (instead of a tree), and each
 *                         tree iter is valid as long as the row in question
 *                         exists, as it only contains a pointer to our struct.
 *
 *****************************************************************************/

static GtkTreeModelFlags
item_list_model_get_flags (GtkTreeModel *tree_model)
{
	g_return_val_if_fail (IS_ITEM_LIST_MODEL(tree_model), (GtkTreeModelFlags)0);

	return (GTK_TREE_MODEL_LIST_ONLY | GTK_TREE_MODEL_ITERS_PERSIST);
}


/*****************************************************************************
 *
 *  item_list_model_get_n_columns: tells the rest of the world how many data
 *                             columns we export via the tree model interface
 *
 *****************************************************************************/

static gint item_list_model_get_n_columns (GtkTreeModel *tree_model)
{
	return 1;
}


/*****************************************************************************
 *
 *  item_list_model_get_column_type: tells the rest of the world which type of
 *                               data an exported model column contains
 *
 *****************************************************************************/

static GType
item_list_model_get_column_type (GtkTreeModel *tree_model,
                                 gint          index)
{
	g_return_val_if_fail (IS_ITEM_LIST_MODEL(tree_model), G_TYPE_INVALID);
	g_return_val_if_fail (index < 1 && index >= 0, G_TYPE_INVALID);
	return G_TYPE_POINTER;
}


/*****************************************************************************
 *
 *  item_list_model_get_iter: converts a tree path (physical position) into a
 *                        tree iter structure (the content of the iter
 *                        fields will only be used internally by our model).
 *                        We simply store a pointer to our CustomRecord
 *                        structure that represents that row in the tree iter.
 *
 *****************************************************************************/

static gboolean
item_list_model_get_iter (GtkTreeModel *tree_model,
                          GtkTreeIter  *iter,
                          GtkTreePath  *path)
{
	ItemListModel  *item_list_model;
	gint           *indices, n, depth;

	g_assert(IS_ITEM_LIST_MODEL(tree_model));
	g_assert(path!=NULL);

	item_list_model = ITEM_LIST_MODEL(tree_model);

	indices = gtk_tree_path_get_indices(path);
	depth   = gtk_tree_path_get_depth(path);

	/* we do not allow children */
	g_assert(depth == 1); /* depth 1 = top level; a list only has top level nodes and no children */

	n = indices[0]; /* the n-th top level row */

	iter->stamp      = item_list_model->stamp;
	iter->user_data  = g_slist_nth (item_list_model->item_list->item_list, n);
	iter->user_data2 = NULL;   /* unused */
	iter->user_data3 = NULL;   /* unused */

	return (iter->user_data) ? TRUE : FALSE;
}


/*****************************************************************************
 *
 *  item_list_model_get_path: converts a tree iter into a tree path (ie. the
 *                        physical position of that row in the list).
 *
 *****************************************************************************/

static GtkTreePath *
item_list_model_get_path (GtkTreeModel *tree_model,
                          GtkTreeIter  *iter)
{
	GSList*   slist;
	GtkTreePath  *path;
	ItemListModel  *item_list_model;

	g_return_val_if_fail (IS_ITEM_LIST_MODEL(tree_model), NULL);
	g_return_val_if_fail (iter != NULL,               NULL);
	g_return_val_if_fail (iter->user_data != NULL,    NULL);

	item_list_model = ITEM_LIST_MODEL(tree_model);

	slist = (GSList*) iter->user_data;

	path = gtk_tree_path_new();
	gtk_tree_path_append_index(path, g_slist_position (item_list_model->item_list->item_list, slist));

	return path;
}


/*****************************************************************************
 *
 *  item_list_model_get_value: Returns a row's exported data columns
 *                         (_get_value is what gtk_tree_model_get uses)
 *
 *****************************************************************************/

static void
item_list_model_get_value (GtkTreeModel *tree_model,
                           GtkTreeIter  *iter,
                           gint          column,
                           GValue       *value)
{
	GSList*  slist;
	ItemListModel    *item_list_model;

	g_return_if_fail (IS_ITEM_LIST_MODEL (tree_model));
	g_return_if_fail (iter != NULL);

	g_value_init (value, G_TYPE_POINTER);

	item_list_model = ITEM_LIST_MODEL(tree_model);

	slist = (GSList*) iter->user_data;

	g_value_set_pointer(value, slist->data);
}


/*****************************************************************************
 *
 *  item_list_model_iter_next: Takes an iter structure and sets it to point
 *                         to the next row.
 *
 *****************************************************************************/

static gboolean
item_list_model_iter_next (GtkTreeModel  *tree_model,
                           GtkTreeIter   *iter)
{
	GSList*  slist;
	ItemListModel    *item_list_model;

	g_return_val_if_fail (IS_ITEM_LIST_MODEL (tree_model), FALSE);

	if (iter == NULL || iter->user_data == NULL)
		return FALSE;

	item_list_model = ITEM_LIST_MODEL(tree_model);

	slist = ((GSList*)iter->user_data)->next;

	if (slist == NULL)
		return FALSE;

	iter->stamp     = item_list_model->stamp;
	iter->user_data = slist;

	return TRUE;
}


/*****************************************************************************
 *
 *  item_list_model_iter_children: Returns TRUE or FALSE depending on whether
 *                             the row specified by 'parent' has any children.
 *                             If it has children, then 'iter' is set to
 *                             point to the first child. Special case: if
 *                             'parent' is NULL, then the first top-level
 *                             row should be returned if it exists.
 *
 *****************************************************************************/

static gboolean
item_list_model_iter_children (GtkTreeModel *tree_model,
                               GtkTreeIter  *iter,
                               GtkTreeIter  *parent)
{
	GSList*  slist;
	ItemListModel  *item_list_model;

	g_return_val_if_fail (parent == NULL || parent->user_data != NULL, FALSE);

	/* this is a list, nodes have no children */
	if (parent)
		return FALSE;

	/* parent == NULL is a special case; we need to return the first top-level row */

	g_return_val_if_fail (IS_ITEM_LIST_MODEL (tree_model), FALSE);

	item_list_model = ITEM_LIST_MODEL(tree_model);

	slist = item_list_model->item_list->item_list;
	if (slist == NULL)
		return FALSE;

	/* Set iter to first item in list */
	iter->stamp     = item_list_model->stamp;
	iter->user_data = slist;

	return TRUE;
}


/*****************************************************************************
 *
 *  item_list_model_iter_has_child: Returns TRUE or FALSE depending on whether
 *                              the row specified by 'iter' has any children.
 *                              We only have a list and thus no children.
 *
 *****************************************************************************/

static gboolean
item_list_model_iter_has_child (GtkTreeModel *tree_model,
                                GtkTreeIter  *iter)
{
	return FALSE;
}


/*****************************************************************************
 *
 *  item_list_model_iter_n_children: Returns the number of children the row
 *                               specified by 'iter' has. This is usually 0,
 *                               as we only have a list and thus do not have
 *                               any children to any rows. A special case is
 *                               when 'iter' is NULL, in which case we need
 *                               to return the number of top-level nodes,
 *                               ie. the number of rows in our list.
 *
 *****************************************************************************/

static gint
item_list_model_iter_n_children (GtkTreeModel *tree_model,
                                 GtkTreeIter  *iter)
{
	ItemListModel  *item_list_model;

	g_return_val_if_fail (IS_ITEM_LIST_MODEL (tree_model), -1);
	g_return_val_if_fail (iter == NULL || iter->user_data != NULL, FALSE);

	item_list_model = ITEM_LIST_MODEL(tree_model);

	/* special case: if iter == NULL, return number of top-level rows */
	if (!iter)
		return g_slist_length (item_list_model->item_list->item_list);

	return 0; /* otherwise, this is easy again for a list */
}


/*****************************************************************************
 *
 *  item_list_model_iter_nth_child: If the row specified by 'parent' has any
 *                              children, set 'iter' to the n-th child and
 *                              return TRUE if it exists, otherwise FALSE.
 *                              A special case is when 'parent' is NULL, in
 *                              which case we need to set 'iter' to the n-th
 *                              row if it exists.
 *
 *****************************************************************************/

static gboolean
item_list_model_iter_nth_child (GtkTreeModel *tree_model,
                                GtkTreeIter  *iter,
                                GtkTreeIter  *parent,
                                gint          n)
{
	GSList*  slist;
	ItemListModel    *item_list_model;

	g_return_val_if_fail (IS_ITEM_LIST_MODEL (tree_model), FALSE);

	item_list_model = ITEM_LIST_MODEL(tree_model);

	/* a list has only top-level rows */
	if(parent)
		return FALSE;

	/* special case: if parent == NULL, set iter to n-th top-level row */

	slist = g_slist_nth (item_list_model->item_list->item_list, n);
	if( slist == NULL )
		return FALSE;

	iter->stamp = item_list_model->stamp;
	iter->user_data = slist;

	return TRUE;
}


/*****************************************************************************
 *
 *  item_list_model_iter_parent: Point 'iter' to the parent node of 'child'. As
 *                           we have a list and thus no children and no
 *                           parents of children, we can just return FALSE.
 *
 *****************************************************************************/

static gboolean
item_list_model_iter_parent (GtkTreeModel *tree_model,
                             GtkTreeIter  *iter,
                             GtkTreeIter  *child)
{
	return FALSE;
}


/*****************************************************************************
 *
 *  item_list_model_new:  This is what you use in your own code to create a
 *                    new custom list tree model for you to use.
 *
 *****************************************************************************/


static void on_item_added (UrlItemList *uilist, UrlItem* uitem, GSList* slist, gpointer data)
{
	GtkTreeModel* model = data;
	GtkTreeIter   iter;
	GtkTreePath*  path;
	int           nth;

	iter.stamp      = ITEM_LIST_MODEL (data)->stamp;
	iter.user_data  = slist;
	iter.user_data2 = NULL;
	iter.user_data3 = NULL;
	nth = g_slist_position (ITEM_LIST_MODEL (data)->item_list->item_list, slist);
	path = gtk_tree_path_new ();
	gtk_tree_path_append_index (path, nth);
	gtk_tree_model_row_inserted (model, path, &iter);
	gtk_tree_path_free (path);
}

ItemListModel* item_list_model_new (UrlItemList* item_list)
{
	ItemListModel *newlist;

	newlist = (ItemListModel*) g_object_new (TYPE_ITEM_LIST_MODEL, NULL);
	g_assert( newlist != NULL );

	url_item_list_ref (item_list);
	newlist->item_list = item_list;
	newlist->added_signal = url_item_list_added_connect (item_list, on_item_added, newlist);

	return newlist;
}

